package db

import (
	"fmt"
	"strings"
	"time"

	"commonUtils/utils"

	"github.com/qiniu/qmgo/options"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/bson/primitive"
)

const (
	// KeyModify 修改时间
	KeyModify = "modify"
	// KeyModifyIP 修改IP
	KeyModifyIP = "modifyip"
	// KeyCreate 创建时间
	KeyCreate = "create"
	// KeyCreateIP 创建IP
	KeyCreateIP = "createip"
)

// BaseMongoService 基本数据服务对象
type BaseMongoService struct {
	*utils.Cache
	idIsObjectID bool
	Db           *MongoClient
	Tablename    string
	Indexes      map[string][]string
}

func NewBaseMongoService(dbobj *MongoClient, tableName string) *BaseMongoService {
	return &BaseMongoService{
		Cache:        utils.NewCache(5*time.Minute, 10*time.Minute),
		Db:           dbobj,
		Tablename:    tableName,
		idIsObjectID: true,
	}
}

func (s *BaseMongoService) IDIsObjectID(b bool) {
	s.idIsObjectID = b
}

func (s *BaseMongoService) Find(where bson.M, out interface{}) error {
	return s.Db.Find(s.Tablename, where, out)
}

func (s *BaseMongoService) FindAndSort(where bson.M, sort []string, out interface{}) error {
	return s.Db.FindAndSort(s.Tablename, where, sort, out)
}

func (s *BaseMongoService) FindOneBySort(where bson.M, sort []string, out interface{}) error {
	return s.Db.FindOneBySort(s.Tablename, where, sort, out)
}

func (s *BaseMongoService) FindOne(where bson.M, out interface{}) error {
	return s.Db.FindOne(s.Tablename, where, out)
}

// GetSort 根据请求参数获取排序
func (s *BaseMongoService) GetSort(data string) []string {
	return strings.Split(data, ",")
}

func (s *BaseMongoService) Page(query bson.M, sort []string, page, size int, data interface{}) (int64, error) {
	skip := (page - 1) * size
	if 0 > skip {
		utils.Error("Page error ,skip < 0 : %v", skip)
		skip = 0
	}

	err := s.Db.Page(s.Tablename, query, sort, skip, size, data)
	if err != nil {
		return 0, err
	}
	total, err := s.Count(query)
	return total, err
}

// func (s *BaseMongoService) Page(query bson.M, sort []string, page, size int) ([]bson.M, int64, error) {
// 	skip := (page - 1) * size
// 	if 0 > skip {
// 		utils.Error("Page error ,skip < 0 : %v", skip)
// 		skip = 0
// 	}
// 	var data []bson.M
// 	err := s.Db.Page(s.Tablename, query, sort, skip, size, &data)
// 	if err != nil {
// 		return data, 0, err
// 	}
// 	total, err := s.Count(query)
// 	return data, total, err
// }

func (s *BaseMongoService) Count(where bson.M) (int64, error) {
	return s.Db.Count(s.Tablename, where)
}

// GetByID 通过id获取数据
func (s *BaseMongoService) GetByID(id interface{}, out interface{}) error {
	var err error
	id, err = s.modifyID(id)
	if err != nil {
		return err
	}
	return s.Db.GetByID(s.Tablename, id, out)
}

func (a *BaseMongoService) GetByAttr(name string, value interface{}, out interface{}) error {
	return a.Db.FindOne(a.Tablename, bson.M{
		name: value,
	}, out)
}

func (s *BaseMongoService) modifyID(id interface{}) (interface{}, error) {
	var err error
	if s.idIsObjectID {
		if idstr, ok := id.(string); ok && utils.IsMongoID(idstr) {
			id, err = primitive.ObjectIDFromHex(idstr)
		}
	}
	return id, err
}

func (s *BaseMongoService) ModifyIDList(ids []string) []interface{} {
	var ret []interface{}
	for _, id := range ids {
		oid, err := s.modifyID(id)
		if err != nil {
			continue
		}
		ret = append(ret, oid)
	}
	return ret
}

func (s *BaseMongoService) UpdateAll(where, data bson.M, ip string) (int64, error) {

	setData, ok := data["$set"].(bson.M)
	if ok && nil != setData {
		setData[KeyModify] = time.Now()
		if "" != ip {
			setData[KeyModifyIP] = ip
		}
	}

	return s.Db.Update(s.Tablename, where, data)
}

func (s *BaseMongoService) SetAll(where, data bson.M, ip string) (int64, error) {
	return s.UpdateAll(where, bson.M{"$set": data}, ip)
}

func (s *BaseMongoService) UpdateByID(id interface{}, data bson.M, ip string) error {
	setData := data["$set"].(bson.M)
	if nil == setData {
		setData = bson.M{}
	}
	setData[KeyModify] = time.Now()
	if "" != ip {
		setData[KeyModifyIP] = ip
	}
	data["$set"] = setData

	var err error
	id, err = s.modifyID(id)
	if err != nil {
		return err
	}
	utils.Debug("UpdateByID id : %s , data : %+v", id, data)
	return s.Db.UpdateID(s.Tablename, id, data)
}

func (s *BaseMongoService) SetDataByID(id interface{}, data bson.M, ip string) error {
	data = bson.M{"$set": data}
	return s.UpdateByID(id, data, ip)
}

func (s *BaseMongoService) GetAttr(id interface{}, key string, out *interface{}) error {
	var data bson.M

	err := s.GetByID(id, &data)
	if err != nil {
		return err
	}
	var ok bool
	var d interface{}
	if d, ok = data[key]; !ok {
		return fmt.Errorf("key %s not exists", key)
	}
	*out = d
	return nil
}

func (s *BaseMongoService) Insert(data bson.M, ip string) error {
	data[KeyCreate] = time.Now()
	data[KeyCreateIP] = ip
	_, err := s.Db.Insert(s.Tablename, data)
	if err == nil {
		// TODO 加入插入日志
	}
	return err
}

func (s *BaseMongoService) Edit(data interface{}, ip string) error {
	err, m := s.ToBson(data)
	if err != nil {
		return err
	}
	return s.DoEdit(m, ip)
}

// DoEdit 修改或添加某条数据
func (s *BaseMongoService) DoEdit(data bson.M, ip string) error {
	id, ok := data[PK]

	isNew := true
	if ok {
		if s.idIsObjectID {
			if id.(primitive.ObjectID).IsZero() {
				data[PK] = primitive.NewObjectID()
			}
		}
		if count, _ := s.Count(bson.M{PK: id}); count == 1 {
			isNew = false
		}
	}

	if isNew {
		data[KeyCreate] = time.Now()
		data[KeyCreateIP] = ip
		_, err := s.Db.Insert(s.Tablename, data)
		if err == nil {
			// TODO 加入插入日志
		}
		return err
	} else {
		data[KeyModify] = time.Now()
		if "" != ip {
			data[KeyModifyIP] = ip
		}
		var query = bson.M{PK: id}
		data = bson.M{"$set": data}
		_, err := s.Db.Update(s.Tablename, query, data)
		if err == nil {
			// TODO 加入修改日志
		}
		return err
	}
}

// Clear 删除beforeTime毫秒以前的数据
func (s *BaseMongoService) Clear(beforeTime int64) (int64, error) {
	deleteTime := time.Now().UTC().Add(-1 * time.Duration(beforeTime) * time.Millisecond)
	query := bson.M{
		KeyCreate: bson.M{
			"$lte": deleteTime,
		},
	}
	return s.DoDelete(query)
}

// DoDelete 删除符合条件的数据
func (s *BaseMongoService) DoDelete(query bson.M) (int64, error) {
	utils.Debug("DoDelete query : %+v", query)
	return s.Db.Delete(s.Tablename, query)
}

// DoDeleteID 删除某条数据
func (s *BaseMongoService) DoDeleteID(id interface{}) error {
	var err error
	id, err = s.modifyID(id)
	if err != nil {
		return err
	}
	return s.Db.DeleteID(s.Tablename, id)
}

func (r *BaseMongoService) Delete(ids []string, bHaveSub bool) (int64, error) {
	var objIDs []interface{}
	var objID interface{}
	var err error
	for _, id := range ids {
		if r.idIsObjectID {
			objID, err = primitive.ObjectIDFromHex(id)
			if err != nil {
				return 0, err
			}
		} else {
			objID = (id)
		}
		objIDs = append(objIDs, objID)
	}
	return r.DeleteByIDS(objIDs, bHaveSub)
}

func (s *BaseMongoService) DeleteByIDS(ids []interface{}, bHaveSubs bool) (int64, error) {

	if len(ids) > 0 {
		var objIDs []interface{}
		for _, id := range ids {
			var err error
			id, err = s.modifyID(id)
			if err != nil {
				return 0, utils.Error("DeleteByIDS id %v parse error : %s", id, err)
			}
			objID := id
			objIDs = append(objIDs, objID)
			if bHaveSubs {
				subs := s.GetAllSubsIds(objID)
				if len(subs) > 0 {
					objIDs = append(objIDs, subs...)
				}
			}
		}
		return s.DoDelete(bson.M{
			"_id": bson.M{
				"$in": objIDs,
			},
		})
	}
	return 0, nil
}

func (s *BaseMongoService) GetAllSubsIds(pid interface{}) []interface{} {
	var ret []interface{}
	var query, err = s.GetSubQuery(pid)
	if err != nil {
		return nil
	}
	var data []bson.M
	err = s.Find(query, &data)
	if err == nil && len(data) > 0 {
		for _, m := range data {
			id := m[PK]
			ret = append(ret, id)
			subs := s.GetAllSubsIds(id)
			if len(subs) > 0 {
				ret = append(ret, subs...)
			}
		}
	} else {
		utils.Error("GetAllSubsIds error : %s", err)
	}
	return ret
}

func (s *BaseMongoService) GetSubQuery(pid interface{}) (bson.M, error) {
	var query bson.M
	p, err := s.modifyID(pid)
	if err != nil {
		return nil, err
	}
	if pid != nil {
		query = bson.M{
			"pid": p,
		}
	} else {
		query = bson.M{
			"$or": bson.A{
				bson.M{
					"pid": bson.M{
						"$exists": false,
					},
				},
				bson.M{
					"pid": primitive.ObjectID{},
				},
			},
		}
	}
	return query, nil
}

func (s *BaseMongoService) SubCount(pid interface{}) (int64, error) {
	p, err := s.GetSubQuery(pid)
	if err != nil {
		return -1, err
	}
	return s.Count(p)
}

// InitIndexes 初始化表的索引（多余的删除，少的添加）
func (s *BaseMongoService) InitIndexes() bool {
	var indexes []*options.IndexModel
	if len(s.Indexes) == 0 {
		return true
	}
	var needIndexes = utils.DeepCopy(s.Indexes).(map[string][]string)
	err := s.Db.Indexes(s.Tablename, &indexes)
	if err == nil {
		bDroped := false
		for _, value := range indexes {
			index := *value.Name
			// keys := value.Key
			if "_id_" == index {
				continue
			}

			if _, ok := needIndexes[index]; ok {
				delete(needIndexes, index)
				continue
			}

			if err := s.Db.DropIndex(s.Tablename, []string{index}); err == nil {
				fmt.Printf("表格【%s】删除索引 : %s 成功 \n", s.Tablename, index)
				bDroped = true
			}

		}
		if bDroped {
			err = s.Db.Indexes(s.Tablename, &indexes)
		}
	}
	for index, value := range needIndexes {
		if err := s.Db.SetIndex(s.Tablename, index, value); err != nil {
			fmt.Printf("表格【%s】添加索引 : %s 失败 err: %s \n", s.Tablename, index, err.Error())
		}
	}

	return true
}

// CheckRepeat 检查键值是否重复
func (s *BaseMongoService) CheckRepeat(key string, value interface{}, id interface{}, where bson.M) (bool, error) {
	if where == nil {
		where = bson.M{}
	}
	where[key] = value
	if !utils.IsNil(id) {
		where["_id"] = bson.M{
			"$nin": []interface{}{id},
		}
	}
	count, err := s.Db.Count(s.Tablename, where)
	if err != nil {
		return false, err
	}
	if count > 0 {
		return false, fmt.Errorf(key + "重复")
	}
	return true, nil
}

func (s *BaseMongoService) CheckPK(id interface{}) bool {

	var err error
	id, err = s.modifyID(id)
	if err != nil {
		return false
	}

	var where = bson.M{
		"_id": id,
	}

	count, _ := s.Count(where)
	return count == 1
}

func (s *BaseMongoService) ToStruct(data bson.M, ret interface{}) error {
	d, err := bson.Marshal(data)
	if err != nil {
		return utils.Error("ToStruct Marshal error : %s", err)
	}
	err = bson.Unmarshal(d, ret)
	if err != nil {
		return utils.Error("ToStruct Unmarshal error : %s", err)
	}
	return nil
}

// ToBson 将struct转为bson
func (s *BaseMongoService) ToBson(data interface{}) (b error, mmap bson.M) {
	mmap = bson.M{}
	bdata, err := bson.Marshal(data)
	if !utils.IsNil(err) {
		b = utils.Error("ToBson error : %s", err)
		return
	}
	err = bson.Unmarshal(bdata, mmap)
	if !utils.IsNil(err) {
		b = utils.Error("ToBson error1 : %s", err)
		return
	}
	return
}

func (s *BaseMongoService) Regex(pattern string, mode string) primitive.Regex {
	return utils.MongoRegex(pattern, mode)
}
